package com.se.nused;

import lombok.extern.log4j.Log4j2;

import java.io.*;
import java.lang.reflect.Constructor;

import java.lang.management.GarbageCollectorMXBean;
import java.lang.management.ManagementFactory;
import java.lang.reflect.Method;
import java.security.ProtectionDomain;
import java.util.List;


/**
 * @Author: hdu_huang
 * @Date: 2019/2/22 14:32
 */
@Log4j2
public class ClassLoadTest {

    /**
     * @author hdu_huang
     * @date   2020/11/30
     * @funs
     *
     * BuiltinClassLoader:  BuiltinClassLoader加载器使用的委派模型与常规委派模型不同
     * 如果是自己模块中存在的类, 直接加载, 否则父类加载, 然后是平台类加载器委托给应用程序类加载器[App]
     *
     * https://zhuanlan.zhihu.com/p/77366251
     */

    public static void main(String[] args) throws Exception{
        log.info(Integer.class.getClassLoader());
        log.info(ClassLoadTest.class.getClassLoader().getParent());

        //ClassLoaders platformClassLoader   BootClassLoader
        ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
        ClassLoader platformClassLoader = ClassLoader.getPlatformClassLoader(); //platformClassLoader.getParent()  null

        // 线程上下文加载器 JDK1.2 默认继承父线程 打破双亲委派
        ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();


        String path = "E:\\usr\\spring-cloud\\client\\target\\classes"; //公司

        String path2 = "F:\\usr\\spring-boot\\client\\target\\classes";
        String path1 = "F:\\usr\\java11_study\\target\\classes";
        SelfClassLoader selfClassLoader = new SelfClassLoader(path);
        ClassLoader parent = selfClassLoader.getParent();


        Class<?> aClass = selfClassLoader.loadClass("com.client.util.MyClassLoader");
        Constructor<?> declaredConstructor = aClass.getDeclaredConstructor();
        declaredConstructor.setAccessible(true);

        //class.newInstance 废弃
//        Object o = declaredConstructor.newInstance();

//        Method method = aClass.getDeclaredMethod("getParent");
//        method.setAccessible(true);
//        Object getParent = method.invoke(o);

        Method defineClass1 = ClassLoader.class.getDeclaredMethod("defineClass1", ClassLoader.class, String.class, byte[].class, int.class, int.class, ProtectionDomain.class, String.class);
        defineClass1.setAccessible(true);

        //com.client.util.BitUtil  java.test.JavaTest[bootstrap 可以加载]
        //java.util.Optional[platformClassLoader可以加载] :  loader 'bootstrap' attempted duplicate class definition for java.util.Optional.
        byte[] bytes = selfClassLoader.getClassByte("java.test.JavaTest");
        Object invoke = defineClass1.invoke(null, platformClassLoader, null, bytes, 0, bytes.length, null, null);  //unnamedModule
//        Object invoke1 = defineClass1.invoke(null, platformClassLoader, null, bytes, 0, bytes.length, null, null);  //platform attempted duplicate class

        List<GarbageCollectorMXBean> garbageCollectors = ManagementFactory.getGarbageCollectorMXBeans();
        garbageCollectors.forEach(garbageCollectorMXBean -> System.out.println(garbageCollectorMXBean.getName()));

    }


}

@Log4j2
class SelfClassLoader extends ClassLoader{

    private String classpath;

    public SelfClassLoader(String classpath){
        this.classpath = classpath;
    }

    static {
        log.error("Hello World!");
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {

        try {
            byte[] classByte = getClassByte(name);

            return defineClass(name, classByte,0, classByte.length);

        } catch (IOException e) {
            e.printStackTrace();
        }

        return Class.forName(name);
    }

    public byte[] getClassByte(String className) throws IOException{
        InputStream in = null;
        ByteArrayOutputStream out = null;
        String path= classpath + File.separatorChar +
                className.replace('.',File.separatorChar)+".class";
        try {
            in=new FileInputStream(path);
            out=new ByteArrayOutputStream();
            byte[] buffer=new byte[2048];
            int len=0;
            while((len=in.read(buffer))!=-1){
                out.write(buffer,0,len);
            }
            return out.toByteArray();
        }
        catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        finally{
            in.close();
            out.close();
        }
        return null;
    }


}

@Log4j2
class Hello {

    static {
        log.error("Hello!");
    }


}